#!/bin/env perl

use lib qw( lib );

#
# Dependencies.
#

use Getopt::Long;
use JSON qw( decode_json to_json );
use List::MoreUtils qw( all );
use LWP::UserAgent;
use Pod::Usage;
use Try::Tiny;
use YAML qw( Dump );

#
# Bitch.
#

use strict;
use warnings;

#
# Options.
#

my %option;
GetOptions ( \%option, qw{
  get put
  
  host=s port=s hash=s
  
  user=s authorization=s specification=s datasheet=s product=s
  
  mails
  branches
  tags
  users
  bugs
  commits
  components
  radiator
  specifications
  datasheets
  devices
  lines
  harvestable
  dr_gte=s
  products
  processes
  
  integrate
  changed=s
  read
  pull
  scope=s
  creation
  milestone=s
  
  report
  product_id=s@
  type_id=s@
  
  repositories
  content=s
  
  repository_id=s
  tag=s
  branch=s
  
  hits
  
  search
  component_id_is=s
  component_id_like=s
  component_type_is=s
  component_type_like=s
  scope_is=s
  scope_like=s
  
  job=s
  harvest=s
  
  column=s
  status=s
  build=s
  
  last_job
  last_harvest
  
  dstp
  deliverable=s
  version=s
  issues
  
  dryrun
  dummy
  debug
  test
  
  json
  
  timeout=s
  
  help
} ) or pod2usage ( 0 );

pod2usage ({ -verbose => 2, -exitval => 0 }) if $option{help};

#
# Arkham.
#

my $put_inferred = $option{pull} || $option{integrate} || $option{milestone} || ( $option{harvest} && $option{build} );

my $crud =
         $put_inferred ? 'PUT'
: $option{get}         ? 'GET'
: $option{put}         ? 'PUT'
:                        'GET';

die 'Missing --get or --put option!' unless $crud;

my $host = $option{host} || $ENV{ISOCK_REST_HOST};
my $port = $option{port} || $ENV{ISOCK_REST_PORT};
my $hash = $option{hash} || $ENV{ISOCK_REST_HASH};

die 'Missing --host option!' unless $host;
die 'Missing --port option!' unless $port;
die 'Missing --hash option!' unless $hash;

#
# Map.
#

my @dispatch = (
  { options => [ qw( users                              ) ], parameters => [ 'users'                                                                                          ] },
  
  { options => [ qw( user authorization product         ) ], parameters => [ "user/{user}/authorization/{authorization}/product/{product}"            , "creation={creation}" ] },
  { options => [ qw( user authorization specification   ) ], parameters => [ "user/{user}/authorization/{authorization}/specification/{specification}", "creation={creation}" ] },
  { options => [ qw( user authorization datasheet       ) ], parameters => [ "user/{user}/authorization/{authorization}/datasheet/{datasheet}",         "creation={creation}" ] },
  
  { options => [ qw( user                               ) ], parameters => [ "user/{user}"                                                                                    ] },
  
  { options => [ qw( datasheet products                 ) ], parameters => [ "datasheet/{datasheet}/products"                                                                 ] },
  
  { options => [ qw( products components scope          ) ], parameters => [ "products/components", "scope={scope}"                                                           ] },
  { options => [ qw( products radiator                  ) ], parameters => [ "products/radiator",                                                                             ] },
  
  { options => [ qw( products                           ) ], parameters => [ 'products'                                                                                       ] },
  
  { options => [ qw( product read scope                 ) ], parameters => [ "product/{product}/read", "scope={scope}"                                                        ] },
  { options => [ qw( product pull scope                 ) ], parameters => [ "product/{product}/pull", "scope={scope}"                                                        ] },
  { options => [ qw( product bugs                       ) ], parameters => [ "product/{product}/bugs", "scope={scope}"                                                        ] },
  { options => [ qw( product components                 ) ], parameters => [ "product/{product}/components"                                                                   ] },
  { options => [ qw( product datasheets                 ) ], parameters => [ "product/{product}/datasheets"                                                                   ] },
  { options => [ qw( product devices                    ) ], parameters => [ "product/{product}/devices"                                                                      ] },
  
  { options => [ qw( specifications                     ) ], parameters => [ 'specifications'                                                                                 ] },
  
  { options => [ qw( specification integrate branch     ) ], parameters => [ "specification/{specification}/integrate?branch={branch}", "changed={changed}"                   ] },
  { options => [ qw( specification integrate            ) ], parameters => [ "specification/{specification}/integrate",                 "changed={changed}"                   ] },
  { options => [ qw( specification mails                ) ], parameters => [ "specification/{specification}/mails"                                                            ] },
  { options => [ qw( specification datasheets           ) ], parameters => [ "specification/{specification}/datasheets"                                                       ] }, # deprecated
  { options => [ qw( specification branches             ) ], parameters => [ "specification/{specification}/branches"                                                         ] },
  { options => [ qw( specification tags                 ) ], parameters => [ "specification/{specification}/tags"                                                             ] },
  { options => [ qw( specification users                ) ], parameters => [ "specification/{specification}/users"                                                            ] },
  { options => [ qw( specification read                 ) ], parameters => [ "specification/{specification}/read"                                                             ] },
  { options => [ qw( specification pull branch          ) ], parameters => [ "specification/{specification}/pull?branch={branch}"                                             ] }, # used by isock_svn_post_commit_hook
  { options => [ qw( specification pull                 ) ], parameters => [ "specification/{specification}/pull"                                                             ] }, # used by isock_svn_post_commit_hook
  { options => [ qw( specification repository_id branch ) ], parameters => [ "specification/{specification}?branch={branch}", "repository_id={repository_id}"                 ] },
  { options => [ qw( specification repository_id tag    ) ], parameters => [ "specification/{specification}?tag={tag}",       "repository_id={repository_id}"                 ] },
  { options => [ qw( specification repository_id        ) ], parameters => [ "specification/{specification}",                 "repository_id={repository_id}"                 ] },
  { options => [ qw( specification milestone            ) ], parameters => [ "specification/{specification}/milestone/{milestone}"                                            ] },
  { options => [ qw( specification                      ) ], parameters => [ "specification/{specification}"                                                                  ] },
  
  { options => [ qw( datasheets                         ) ], parameters => [ 'datasheets'                                                                                     ] },
  
  { options => [ qw( datasheet mails                    ) ], parameters => [ "datasheet/{datasheet}/mails"                                                                    ] },                   
  { options => [ qw( datasheet users                    ) ], parameters => [ "datasheet/{datasheet}/users"                                                                    ] },
  { options => [ qw( datasheet commits                  ) ], parameters => [ "datasheet/{datasheet}/commits"                                                                  ] },
  { options => [ qw( datasheet repository_id branch     ) ], parameters => [ "datasheet/{datasheet}?branch={branch}", "repository_id={repository_id}"                         ] },
  { options => [ qw( datasheet repository_id tag        ) ], parameters => [ "datasheet/{datasheet}?tag={tag}",       "repository_id={repository_id}"                         ] },
  { options => [ qw( datasheet repository_id            ) ], parameters => [ "datasheet/{datasheet}",                 "repository_id={repository_id}"                         ] },
  
  { options => [ qw( repositories                       ) ], parameters => [ 'repositories', 'content={content}'                                                              ] },
  
  { options => [ qw( dstp deliverable version issues    ) ], parameters => [ "dstp/deliverable/{deliverable}/version/{version}/issues"                                        ] },
  
  { options => [ qw( components report                  ) ], parameters => [ "components/report", "scope={scope}&product_ids={product_id}&type_ids={type_id}"                 ] },
  
  { options => [ qw( components                         ) ], parameters => [ 'components'                                                                                     ] },
  
  { options => [ qw( devices                            ) ], parameters => [ "devices/pull"                                                                                   ] },
  
  { options => [ qw( lines                              ) ], parameters => [ "lines/harvestable", 'dr_gte={dr_gte}'                                                           ] },
  
  { options => [ qw( processes                          ) ], parameters => [ 'processes'                                                                                      ] },
  
  { options => [ qw( hits                               ) ], parameters => [ 'hits'                                                                                           ] },
  
  { options => [ qw( job column                         ) ], parameters => [ 'job/{job}/column/{column}'                                                                      ] },
  { options => [ qw( job                                ) ], parameters => [ 'job/{job}'                                                                                      ] },
  
  { options => [ qw( last_job                           ) ], parameters => [ 'last_job'                                                                                       ] },
  
  { options => [ qw( harvest column                     ) ], parameters => [ 'harvest/{harvest}/column/{column}'                                                              ] },
  { options => [ qw( harvest status                     ) ], parameters => [ 'harvest/{harvest}/status/{status}'                                                              ] },
  { options => [ qw( harvest build                      ) ], parameters => [ 'harvest/{harvest}/build/{build}'                                                                ] },
  { options => [ qw( harvest                            ) ], parameters => [ 'harvest/{harvest}'                                                                              ] },
  
  { options => [ qw( last_harvest                       ) ], parameters => [ 'last_harvest'                                                                                   ] },
  
  { options => [ qw( search                             ) ], parameters => [ 'search', join ( '&', map { "$_={$_}" } qw(
    component_id_is
    component_id_like
    component_type_is
    component_type_like
    scope_is
    scope_like
  ) ) ] },
);

#
# Core.
#

my ( $query ) = grep { all { defined $option{$_} } @{$_->{options}} } @dispatch;
if ( $query ) {
  no warnings 'uninitialized';
  
  my ( $path, $tail ) = map { s/ \{ (\w+) \} /ref $option{$1} eq 'ARRAY' ? join '|', @{$option{$1}} : $option{$1}/exg ; $_ } @{$query->{parameters}};
    
  if ( $tail =~ / ( scope ) = $ /x ) {
    die "missing '$1' parameter value!";
  }
  
  $tail =~ s/ [^&=]+ = $ //xg;
  
  query ( $crud => $path, $tail ); 
}
else {
  die 'Unsupported query!';
}

#
# Subs.
#

sub query {
  my ( $crud, $path, $tail ) = @_;
  
  if ( my $ua = LWP::UserAgent->new ) {
    my $url = "http://$host:$port/rest/$path" . ( $path =~ /\?/ ? "&" : "?" ) . "hash=$hash"
    . ( $option{dummy} ? "&dummy=1" : "" )
    . ( $option{debug} ? "&debug=1" : "" )
    . ( $option{test}  ? "&test=1"  : "" )
    . ( $tail          ? "&$tail"   : "" )
    ;
    
    if ( $option{dryrun} ) {
      print "# $crud $url\n";
    }
    else {
      print "# $crud $url ...\n";

      $ua->no_proxy ( $host );
      $ua->timeout ( $option{timeout} || ( 10 * 60 ) ); # 180

      if ( my $request = HTTP::Request->new ( $crud => $url ) ) {
        $request->content_type ( 'application/json' );

        if ( my $response = $ua->request ( $request ) ) {
          my $content;

          try {
            $content = decode_json ( $response->content );
          };

          if ( $response->is_success ) {
            if ( $option{json} ) {
              print to_json ( $content, { ascii => 1, pretty => 1 } ); # utf8
            }
            else {
              print Dump ( $content );
            }
          }
          else {
            die $content->{error} || $response->status_line;
          }
        }
        else {
          die 'Could not get any response!';
        }
      }
      else {
        die 'Could not build request!';
      }

      print "# DONE\n";
    }
  }
  else {
    die 'Could not build user agent!';
  }
}

#
# Documentation.
#

=head1 NAME

isock_restful_query

=head1 USAGE

    isock_restful_query --hash $hash [--put] [--timeout 600] [--json] OPTIONS

=head1 SYNOPSIS

iSOCK CLI via RESTful queries.

Defaults to GET kind of query (else use --put) and YAML output (else use --json).

=head1 REQUIRED ARGUMENTS

=over

=item --hash $hash    

Your RESTful API hash (hexadecimal SHA1). Get it from iSOCK root.

=back

=head1 QUERIES

=head2 SPECIFICATION

=over

=item List

    isock_restful_query --specifications

Returns a list of known specifications.

=item Properties

    isock_restful_query --specification $specification

=item List of repository branches

    isock_restful_query --specification $specification --branches

Returns a naturally sorted list of repository branches.

=item List of repository tags

    isock_restful_query --specification $specification --tags

Returns a naturally sorted list of repository tags.

=item List of user mails

    isock_restful_query --specification $specification --mails

Returns a list of users following the specification (owner => writer => follower).

=item List of datasheets

    isock_restful_query --specification $specification --datasheets

Returns a list of datasheets linked to specification in iSOCK DB.

=item Trigger CI

    isock_restful_query --specification $specification --integrate --put [--branch $branch] [--changed sock|specification|follow-up] [--dummy] [--test]

=item User authorization

    isock_restful_query --specification $specification --authorization owner|writer|follower|tinkerer --user $user

=item Push into iSOCK DB

    isock_restful_query --specification $specification --repository_id $repository_id [--branch $branch] [--tag $tag]

Use C<isock_restful_query --repositories --content specification> to get a list of known repositories.

=item Pull component lineup into iSOCK DB

  isock_restful_query --specification $specification --pull --put [--branch $branch]

Reads specification files (MIF and XLS) and populates linked product(s) component freezes.

=item Component lineup

  isock_restful_query --specification $specification --read

Reads product specification files (MIF and XLS) but does not alter iSOCK DB.

=item Milestone

  isock_restful_query --specification $specification --milestone ARM|TOOLS

Creates a milestone, i.e., a glorified ${milestone}-YYYYMMDDTHHMMSS tag.

=back

=head2 DATASHEET

=over

=item List

    isock_restful_query --datasheets

Returns a list of known datasheets.

=item Push into iSOCK DB

    isock_restful_query --datasheet $datasheet --repository_id $repository_id [--branch $branch] [--tag $tag]

Use C<isock_restful_query --repositories --content datasheet> to get a list of known repositories.

=item Related products

    isock_restful_query --datasheet $datasheet --products

=back

=head2 PRODUCT

=over

=item List

    isock_restful_query --products

Returns a list of known products.

=item Products radiator

    isock_restful_query --products --radiator

Returns a LoH for radiator purposes.

=item Products X Components

    isock_restful_query --products --component --scope specification|integration

Returns a list of products-to-components LoL, though mostly suitable for HTML rendering.

=item Component lineup

    isock_restful_query --product $product --read --scope specification|integration

Reads product specification files (MIF and XLS) but does not alter iSOCK DB.

=item Bugs

    isock_restful_query --product $product --bugs --scope specification|integration

Reads bugs list.

=item Datasheets

    isock_restful_query --product $product --datasheets

Reads datasheets list.

=item Devices

    isock_restful_query --product $product --devices

Reads devices list.

=back

=head2 COMPONENT

=over

=item List

    isock_restful_query --components

=item Report

    isock_restful_query --components --report --scope specification|integration --product_id $product [ --product_id $product ] [ --type_id $type [ --type_id $type ] ]

=back

=head2 DEVICE

=over

=item List

    isock_restful_query --devices

B<NOT YET IMPLEMENTED!>

=item Pull

    isock_restful_query --devices --pull

Parses L<http://www.atmel.com/> site DB serialization to populate devices columns.

=back

=head2 LINE

=over

=item List

    isock_restful_query --lines

B<NOT YET IMPLEMENTED!>

=item Harvestable

    isock_restful_query --lines --harvestable [ --dr_gte DR4 ]

Returns a complex data structure about harvestable lines (aka "series").

=back


=head2 USER

=over

=item List

    isock_restful_query --users

Returns a list of known users.

=item Properties

    isock_restful_query --user $user

=item Specification authorization

    isock_restful_query --user $user --specification $specification --authorization owner|writer|follower|tinkerer

=back

=head2 PROCESS

=over

=item List

    isock_restful_query --processes

=back

=head2 SEARCH

=over

=item Products

    isock_restful_query \
        [ ( --component_id_is   | --component_id_like   ) $component_id   ] \
        [ ( --component_type_is | --component_type_like ) $component_type ] \
        [ ( --scope_is          | --scope_like          ) $scope          ] \
        [ --debug ]

To search for products containing a given set of components.

Current component types are datasheet, ip, analog, digital and memory.

Current scopes are specification and integration.

=back

=head2 JOB

=over

=item Columns

    isock_restful_query --job $job

=item Column

    isock_restful_query --job $job --column $column

=back

=head2 LAST JOB

=over

=item Columns

    isock_restful_query --last_job

=back

=head2 HARVEST

=over

=item Columns

    isock_restful_query --harvest $harvest

=item Harvested (PUT)

    isock_restful_query --harvest $harvest --build $build

Where $build is remote (e.g., Jenkins) build ID.

=item Completed (PUT)

    isock_restful_query --harvest $harvest --put

=back

=head2 LAST HARVEST

=over

=item Columns

    isock_restful_query --last_harvest

=back

=head2 META

=over

=item Hits

    isock_restful_query --hits

=back

=head1 AUTHOR

Xavier Caron, C<< <xavier.caron at atmel.com> >>

=head1 BUGS

There are undoubtedly serious bugs lurking somewhere in this code.
Bug reports and other feedback are most welcome.

=head1 COPYRIGHT

Copyright 2007-2012 Atmel Corp., all rights reserved.

This module is free software. It may be used, redistributed
and/or modified under the terms of the Perl Artistic License
(see http://www.perl.com/perl/misc/Artistic.html)

=cut
